/*
 * Copyright (C) 2022, KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "menu-main-window.h"
#include "settings.h"
#include "context-menu-manager.h"

#include <QGuiApplication>
#include <QQuickItem>
#include <QGSettings>
#include <QX11Info>
#include <QQmlContext>
#include <QPoint>
#include <KWindowSystem>
#include <KWindowEffects>

// kysdk
#include <kysdk/applications/windowmanager/windowmanager.h>
#include <kysdk/applications/ukuistylehelper/ukui-decoration-manager.h>

// x11
#include <X11/Xlib.h>

#define UKUI_PANEL_SETTING          "org.ukui.panel.settings"
#define UKUI_PANEL_POSITION_KEY     "panelposition"
#define UKUI_PANEL_SIZE_KEY         "panelsize"

namespace UkuiMenu {

struct MotifWmHints {
    ulong flags       = 0;
    ulong functions   = 0;
    ulong decorations = 0;
    long  input_mode  = 0;
    ulong status      = 0;
};

#define MWM_HINTS_FUNCTIONS     (1L << 0)
#define MWM_HINTS_DECORATIONS   (1L << 1)
#define MWM_FUNC_ALL            (1L << 0)
#define MWM_DECOR_BORDER        (1L << 1)

void WindowModule::defineModule(const char *uri, int versionMajor, int versionMinor)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
    qmlRegisterRevision<QWindow, 3>(uri, versionMajor, versionMinor);
    qmlRegisterRevision<QQuickWindow, 2>(uri, versionMajor, versionMinor);
#else
#if QT_VERSION >= QT_VERSION_CHECK(5, 3, 0)
    qmlRegisterRevision<QWindow, 2>(uri, versionMajor, versionMinor);
    qmlRegisterRevision<QQuickWindow, 2>(uri, versionMajor, versionMinor);
#else
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    qmlRegisterRevision<QWindow, 2>(uri, versionMajor, versionMinor);
    qmlRegisterRevision<QQuickWindow, 1>(uri, versionMajor, versionMinor);
#else
    qmlRegisterRevision<QWindow, 1>(uri, versionMajor, versionMinor);
#endif
#endif
#endif
}

void WindowHelper::setWindowGeometry(QWindow *window, const QRect &rect)
{
    if (!window) {
        return;
    }

    // 窗口系统不存在跟屏幕一样大的普通窗口？
    window->setWindowState(Qt::WindowNoState);
    if (QX11Info::isPlatformX11()) {
        window->setGeometry(rect);

    } else {
        kdk::WindowManager::setGeometry(window, rect);
    }
}

// 窗口管理器属性
void WindowHelper::setWindowAttribute(QWindow *window)
{
    if (!window) {
        return;
    }

    // kwin
    KWindowSystem::setType(window->winId(), NET::SystemWindow);
    // 设置跳过多任务视图，设置跳过任务栏
    kdk::WindowManager::setSkipTaskBar(window, true);
    kdk::WindowManager::setSkipSwitcher(window, true);
}

void WindowHelper::removeHeaderBar(QWindow *window)
{
    if (!window) {
        return;
    }

    if (QX11Info::isPlatformX11()) {
        MotifWmHints hints;
        hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
        hints.functions = MWM_FUNC_ALL;
        hints.decorations = MWM_DECOR_BORDER;

        unsigned long atom = XInternAtom(QX11Info::display(), "_MOTIF_WM_HINTS", true);
        XChangeProperty(QX11Info::display(), window->winId(), atom, atom, 32, XCB_PROP_MODE_REPLACE,
                        (const unsigned char *)&hints, sizeof(MotifWmHints) / sizeof(const unsigned char));

    } else {
        UKUIDecorationManager::getInstance()->removeHeaderBar(window);
    }
}

void WindowHelper::windowBlur(QWindow *window, bool enable, const QRegion &region)
{
    if (!window) {
        return;
    }
    //use KWindowEffects
    KWindowEffects::enableBlurBehind(window->winId(), enable, region);
}

// ====== WindowGeometryHelper ====== //
WindowGeometryHelper::WindowGeometryHelper(QObject *parent) : QObject(parent)
{
    initPanelSetting();
    initScreenMonitor();
    updateGeometry();

    connect(MenuSetting::instance(), &MenuSetting::changed, this, [this] (const QString& key) {
        if (key == MENU_WIDTH || key == MENU_HEIGHT) {
            updateGeometry();
        }
    });
}

void WindowGeometryHelper::updateGeometry()
{
    if (!m_primaryScreen) {
        return;
    }
    QRect screenRect = m_primaryScreen->geometry(), normalRect, fullRect;

    int width = MenuSetting::instance()->get(MENU_WIDTH).toInt();
    int height = MenuSetting::instance()->get(MENU_HEIGHT).toInt();
    int margin = MenuSetting::instance()->get(MENU_MARGIN).toInt();

    if (width > screenRect.width()) {
        width = screenRect.width();
    }
    if (height > screenRect.height()) {
        height = screenRect.height();
    }


    //上: 1, 下: 0, 左: 2, 右: 3
    switch (m_panelPos) {
        case 0: {
            QPoint topLeft;
            topLeft.setX(screenRect.x() + margin);
            topLeft.setY(screenRect.y() + screenRect.height() - height - margin - m_panelSize);

            normalRect.setTopLeft(topLeft);
            normalRect.setSize({width, height});

            fullRect.setTopLeft(screenRect.topLeft());
            fullRect.setSize({screenRect.width(), screenRect.height() - m_panelSize});

            break;
        }
        case 1: {
            normalRect.setTopLeft({screenRect.x() + margin, screenRect.y() + margin + m_panelSize});
            normalRect.setSize({width, height});

            fullRect.setTopLeft({screenRect.x(), screenRect.y() + m_panelSize});
            fullRect.setSize({screenRect.width(), screenRect.height() - m_panelSize});

            break;
        }
        case 2: {
            normalRect.setTopLeft({screenRect.x() + margin + m_panelSize, screenRect.y() + margin});
            normalRect.setSize({width, height});

            fullRect.setTopLeft({screenRect.x() + m_panelSize, screenRect.y()});
            fullRect.setSize({screenRect.width() - m_panelSize, screenRect.height()});
            break;
        }
        case 3: {
            QPoint topLeft;
            topLeft.setX(screenRect.x() + screenRect.width() - width - margin - m_panelSize);
            topLeft.setY(screenRect.y() + margin);

            normalRect.setTopLeft(topLeft);
            normalRect.setSize({width, height});

            fullRect.setTopLeft(screenRect.topLeft());
            fullRect.setSize({screenRect.width() - m_panelSize, screenRect.height()});
            break;
        }
        default: {
            normalRect.setTopLeft({screenRect.x() + margin, screenRect.y() + screenRect.height() - height - margin});
            normalRect.setSize({width, height});

            fullRect.setTopLeft({screenRect.x(), screenRect.y()});
            fullRect.setSize({screenRect.width(), screenRect.height()});
            break;
        }
    }

    m_normalGeometry = normalRect;
    m_fullScreenGeometry = fullRect;

    Q_EMIT geometryChanged();
}

void WindowGeometryHelper::initPanelSetting()
{
    if (!MenuSetting::instance()->get(FOLLOW_UKUI_PANEL).toBool()) {
        return;
    }

    const QByteArray id(UKUI_PANEL_SETTING);
    if (QGSettings::isSchemaInstalled(id)) {
        QGSettings *setting = new QGSettings(id, QByteArray(), this);

        QStringList keys = setting->keys();
        if (keys.contains(UKUI_PANEL_POSITION_KEY)) {
            m_panelPos = setting->get(UKUI_PANEL_POSITION_KEY).toInt();
        }
        if (keys.contains(UKUI_PANEL_SIZE_KEY)) {
            m_panelSize = setting->get(UKUI_PANEL_SIZE_KEY).toInt();
        }

        connect(setting, &QGSettings::changed, this, [this, setting] (const QString& key) {
            if (key == UKUI_PANEL_POSITION_KEY || key == UKUI_PANEL_SIZE_KEY) {
                m_panelPos = setting->get(UKUI_PANEL_POSITION_KEY).toInt();
                m_panelSize = setting->get(UKUI_PANEL_SIZE_KEY).toInt();
                updateGeometry();
            }
        });
    }
}

void WindowGeometryHelper::initScreenMonitor()
{
    updatePrimaryScreen(QGuiApplication::primaryScreen());
    connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, [this] (QScreen *screen) {
        updatePrimaryScreen(screen);
        updateGeometry();
    });
}

void WindowGeometryHelper::updatePrimaryScreen(QScreen *screen)
{
    if (!screen) {
        return;
    }

    if (m_primaryScreen) {
        disconnect(m_primaryScreen);
    }

    m_primaryScreen = screen;
    connect(m_primaryScreen, &QScreen::geometryChanged, this, &WindowGeometryHelper::updateGeometry);
}

const QRect &WindowGeometryHelper::fullScreenGeometry()
{
    return m_fullScreenGeometry;
}

const QRect &WindowGeometryHelper::normalGeometry()
{
    return m_normalGeometry;
}

const int WindowGeometryHelper::getPanelPos()
{
    return m_panelPos;
}

//======MenuWindow======//
MenuWindow::MenuWindow(QWindow *parent) : QQuickView(parent), m_geometryHelper(new WindowGeometryHelper(this))
{
    init();
}

MenuWindow::MenuWindow(QQmlEngine *engine, QWindow *parent)
    : QQuickView(engine, parent), m_geometryHelper(new WindowGeometryHelper(this))
{
    init();
}

void MenuWindow::init()
{
    setTitle(QCoreApplication::applicationName());
    setResizeMode(SizeRootObjectToView);
    setColor("transparent");

//    setFlags(Qt::FramelessWindowHint);
    setFlags(Qt::Window);
    WindowHelper::setWindowAttribute(this);
    WindowHelper::removeHeaderBar(this);
    WindowHelper::windowBlur(this, true);

    // 访问窗口api
    rootContext()->setContextProperty("mainWindow", this);

    connect(m_geometryHelper, &WindowGeometryHelper::geometryChanged, this, [this] {
        QEvent event(QEvent::Move);
        QCoreApplication::sendEvent(this, &event);
        Q_EMIT panelPosChanged();
    });

    rootContext()->setContextProperty("isLiteMode", GlobalSetting::instance()->get(GlobalSetting::IsLiteMode));
    connect(GlobalSetting::instance(), &GlobalSetting::styleChanged, this , [this] (const GlobalSetting::Key& key) {
        if (key == GlobalSetting::EffectEnabled) {
            Q_EMIT effectEnabledChanged();
        } else if (key == GlobalSetting::Transparency) {
            Q_EMIT transparencyChanged();
        } else if (key == GlobalSetting::IsLiteMode) {
            rootContext()->setContextProperty("isLiteMode", GlobalSetting::instance()->get(key));
        }
    });

    updateGeometry();

    ContextMenuManager::instance()->setMainWindow(this);
}

void MenuWindow::updateGeometry()
{
    QRect rect = m_isFullScreen ? m_geometryHelper->fullScreenGeometry() : m_geometryHelper->normalGeometry();
    if (rect == geometry()) {
        return;
    }

    if (m_isFullScreen) {
        setMaximumSize(rect.size());
    } else {
        setMinimumSize(rect.size());
    }

    WindowHelper::setWindowGeometry(this, rect);
    setMinimumSize(geometry().size());
    setMaximumSize(geometry().size());
}

bool MenuWindow::isFullScreen() const
{
    return m_isFullScreen;
}

QRect MenuWindow::normalScreenSize()
{
    return m_geometryHelper->normalGeometry();
}

/**
 * beforeFullScreenChanged -> (qml)onWidthChanged -> fullScreenChanged
 * @param isFullScreen
 */
void MenuWindow::setFullScreen(bool isFullScreen)
{
    if (m_isFullScreen == isFullScreen) {
        return;
    }

    m_isFullScreen = isFullScreen;

    WindowHelper::windowBlur(this, !m_isFullScreen);

    updateGeometry();

    // 更新contentItem尺寸
    QEvent event(QEvent::Resize);
    QCoreApplication::sendEvent(this, &event);

    Q_EMIT fullScreenChanged();
}

void MenuWindow::changeWindowBlurRegion(int x, int y, int width, int height)
{
    QRegion region(x, y, width, height);
    WindowHelper::windowBlur(this, true, region);
}

void MenuWindow::exitFullScreen()
{
    Q_EMIT beforeFullScreenExited();
}

void MenuWindow::enableWindowBlur(bool enable)
{
    WindowHelper::windowBlur(this, enable);
}

void MenuWindow::exposeEvent(QExposeEvent *event)
{
    if (isExposed()) {
        if (QX11Info::isPlatformX11()) {
            requestActivate();
        }
        WindowHelper::setWindowAttribute(this);
//            WindowHelper::removeHeaderBar(this);
        updateGeometry();
    }
    QQuickView::exposeEvent(event);
}

void MenuWindow::focusOutEvent(QFocusEvent *event)
{
    // void QQuickWindow::focusOutEvent(QFocusEvent *ev) { Q_D(QQuickWindow); if (d->contentItem)  d->contentItem->setFocus(false, ev->reason()); }
    QQuickView::focusOutEvent(event);
    // setVisible(false);
}

bool MenuWindow::event(QEvent *event)
{
    if (event->type() == QEvent::Move) {
        updateGeometry();
        return true;
    }
    if (event->type() == QEvent::UpdateRequest) {
        WindowHelper::removeHeaderBar(this);
    }
    if (event->type() == QEvent::MouseButtonPress) {
        ContextMenuManager::instance()->closeMenu();
    }

    return QQuickView::event(event);
}

void MenuWindow::showEvent(QShowEvent *event)
{
    //为了主动触发一次updaterequet事件
    update();
    QQuickView::showEvent(event);
}

bool MenuWindow::effectEnabled() const
{
    return GlobalSetting::instance()->get(GlobalSetting::EffectEnabled).toBool();
}

double MenuWindow::transparency() const
{
    return GlobalSetting::instance()->get(GlobalSetting::Transparency).toDouble();
}

int MenuWindow::panelPos() const
{
    return m_geometryHelper->getPanelPos();
}

bool MenuWindow::editMode() const
{
    return m_editMode;
}

void MenuWindow::setEditMode(bool mode)
{
    if (mode == m_editMode) {
            return;
        }

    m_editMode = mode;
    Q_EMIT editModeChanged();
}

} // UkuiMenu
